package com.weilive.core.netty.handler;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.weilive.core.database.DynamicDataSource;
import com.weilive.core.event.EventDispatcher;
import com.weilive.core.filter.LoginFilter;
import com.weilive.core.netty.NettyErrors;
import com.weilive.core.netty.NettyEvents;
import com.weilive.core.netty.NettyException;
import com.weilive.core.netty.NettyRequestHandler;
import com.weilive.core.protocol.protobuf.ProtobufRequest;
import com.weilive.core.protocol.protobuf.ProtobufRequestLocal;
import com.weilive.core.protocol.protobuf.ProtobufResponse;

public class SimpleProtobufHandler extends NettyRequestHandler {

	private static final Logger log = LoggerFactory.getLogger(SimpleProtobufHandler.class);

	@Override
	public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		EventDispatcher.sendEvent(new NettyEvents.Open(ctx.getChannel()));
	}

	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		EventDispatcher.sendEvent(new NettyEvents.Close(ctx.getChannel()));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		EventDispatcher.sendEvent(new NettyEvents.OnError(e.getChannel(), e.getCause()));
		try {
			e.getChannel().close();
		} catch (Exception ex) {
		}
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent msg) throws Exception {
		DynamicDataSource.clear();
		ProtobufRequestLocal.clear();
		long current = System.currentTimeMillis();
		Object message = msg.getMessage();
		if (message instanceof ProtobufRequest) {
			ProtobufRequest request = (ProtobufRequest) message;

			log.info("Receive socket protobuf request: " + request);
			if (LoginFilter.doFilter(request) == false) {
				return;
			}
			ProtobufRequestLocal.setRequest(request);
			ProtobufResponse response = null;
			try {
				response = request.handler.handleRequest(request);
			} catch (NettyException ex) {
				response = ProtobufResponse.create(ex.getError(), request.handler.getResponseType());
			} catch (Exception e) {
				response = ProtobufResponse.create(NettyErrors.CODE_SERVER_ERROR, request.handler.getResponseType());
			}
			if (response != null) {
				request.channel.write(response);
				log.info("Send socket protobuf response: " + response);
			}
		}
		EventDispatcher.sendEvent(new NettyEvents.OnMessage(System.currentTimeMillis() - current, ctx.getChannel(), message));
	}

	/**
	 * ------------------------------------------------------------------------
	 * -------- -- Encoder / Decoder / FrameDecoder
	 * ------------------------------
	 * --------------------------------------------------
	 */

	public static class ProtobufEncoder extends OneToOneEncoder {

		@Override
		protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
			if (msg instanceof ProtobufResponse) {
				ChannelBuffer buffer = ((ProtobufResponse) msg).encode();
				int length = buffer.readableBytes();
				byte head[] = new byte[4];
				head[0] = (byte) ((length) & 0xff);
				head[1] = (byte) ((length >> 8) & 0xff);
				head[2] = (byte) ((length >> 16) & 0xff);
				head[3] = (byte) ((length >> 24) & 0xff);
				return ChannelBuffers.wrappedBuffer(ChannelBuffers.wrappedBuffer(head), buffer);
			}
			return msg;
		}
	}

	public static class ProtobufFrameDecoder extends FrameDecoder {
		@Override
		protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
			if (buffer.readableBytes() < 4) {
				return null;
			}
			buffer.markReaderIndex();
			int length = (((int) buffer.readByte() & 0xff)) | (((int) buffer.readByte() & 0xff) << 8) | (((int) buffer.readByte() & 0xff) << 16) | (((int) buffer.readByte() & 0xff) << 24);
			if (buffer.readableBytes() >= length) {
				return buffer.readBytes(length);
			}
			buffer.resetReaderIndex();
			return null;
		}
	}

	public static class ProtobufDecoder extends OneToOneDecoder {
		@Override
		protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
			if (!(msg instanceof ChannelBuffer)) {
				return msg;
			}
			return ProtobufRequest.decode(channel, (ChannelBuffer) msg);
		}
	}
}
